package cn.xiaobai.monitor;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.xiaobai.domain.ConfInfo;
import cn.xiaobai.domain.PushChannel;
import cn.xioabai.common.utility.IOUtils;
import cn.xioabai.common.utility.JsonUtils;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.io.File;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 监控器服务, 处理数据重置和更新
 *
 * @Author yangdaji
 * @Date 2022/11/12
 * @PackageName xiaobai.service
 * @ClassName MonitorServer
 */
@Slf4j
@Component
public class MonitorServer {

    /**
     * 频道别名映射, key: 原始推送频道 value: 频道别名
     */
    private Map<String, String> channelToMap = new ConcurrentHashMap<>();

    /**
     * redis 推送数据, key: 推送频道 value: 推送数据
     */
    private Map<String, Object> channelToDataMap = new ConcurrentHashMap<>();

    /**
     * 初始化推送数据
     *
     * @param fileList 文件列表
     */
    public void initPushData(List<File> fileList) {
        // 批量加载推送数据
        if (!CollectionUtils.isEmpty(fileList)) {
            fileList.forEach(this::loadPushData);
        }
    }

    /**
     * 初始化频道映射
     *
     * @param fileList 文件列表
     */
    public void initChannel(List<File> fileList) {
        // 批量加载推送数据
        if (!CollectionUtils.isEmpty(fileList)) {
            fileList.forEach(this::loadChannelToDataMap);
        }
    }

    /**
     * 加载推送数据, 频道使用从根目录(不包含根目录)开始的所有目录使用点加上文件名
     *
     * @param file 文件
     */
    public void loadPushData(File file) {
        log.info("加载推送数据....");
        // 设置层级+ 文件名 作为 key
        String listenerFile = ConfInfo.listenerFile;
        String fileName;
        String parentPath = file.getParent().replace("\\", "/");
        // 比较父级目录是否是根目录, 将不是同一个父级目录的文件做 分级操作
        if (!listenerFile.equals(parentPath)) {
            // 将除监听根目录的所有层级名称按照 . 进行拼接
            fileName = resetPushPath(file, listenerFile);
        } else {
            fileName = IOUtils.getFileName(file);
        }
//        log.info("推送的原始频道: {}", fileName);
        if (Strings.isNotBlank(fileName)) {
            String jsonData = IOUtils.readFileToString(file);
            // 格式是 json/jsonList
            if (JsonUtils.isJsonListString(jsonData) || JsonUtils.isJsonString(jsonData)) {
                Object jsonObj = JsonUtils.readValue(jsonData,
                        new TypeReference<Object>() {
                        });
                // 通过原始记录刷新映射key的数据
                if (CollectionUtil.isNotEmpty(channelToMap)) {
                    if (channelToMap.containsKey(fileName)) {
                        String key = channelToMap.get(fileName);
                        channelToDataMap.put(key, jsonObj);
                    }
                } else {
                    channelToDataMap.put(fileName, jsonObj);
                }
            }
        }
    }

    /**
     * 父级目录使用点连接,直达目标目录,不包含目标目录
     *
     * @param file       文件
     * @param targetPath 目标目录
     * @return
     */
    private String resetPushPath(File file, String targetPath) {

        // 获取文件父级目录
        String parent = file.getParent();
        if (Strings.isBlank(parent)) {
            return null;
        }
        // 根据目标目录分割
        String[] pathName = parent.replace("\\", "/").split(targetPath);
        if (ArrayUtil.isEmpty(pathName)) {
            return null;
        }
        StringBuilder stringBuilder = new StringBuilder();
        for (String name : pathName) {
            if (!Strings.isBlank(name)) {
                String[] buildPathName = name.split("/");
                for (String str : buildPathName) {
                    if (!Strings.isBlank(str)) {
                        stringBuilder.append(str).append(".");
                    }
                }
            }
        }
        // 拼接上文件名
        return stringBuilder.append(IOUtils.getFileName(file)).toString();
    }

    /**
     * 加载频道映射到数据推送映射表中
     *
     * @param file 文件
     */
    public void loadChannelToDataMap(File file) {

        log.info("加载映射文件中: {}", file);
        String jsonData = IOUtils.readFileToString(file);
        List<PushChannel> pushChannelList = JsonUtils.readValue(jsonData,
                new TypeReference<List<PushChannel>>() {
                });

        // 加载需要往redis 推送的数据
        loadChooseRedisPushDataToMap(pushChannelList);
    }

    /**
     * 加载redis 推送的数据
     */
    private void loadChooseRedisPushDataToMap(List<PushChannel> pushChannelList) {
        // 重新定义推送频道, 将自定义映射的频道进行更新
        if (!CollectionUtils.isEmpty(pushChannelList)) {
            log.info("加载映射,为原始推送频道设置别名....");
            pushChannelList.forEach(channel -> {
                String key = channel.getFileName();
                String newKey = channel.getChannel();
                String oldChannel = null;
                // 获取原始频道
                if (channelToMap.containsKey(key)) {
                    oldChannel = channelToMap.get(key);
                    channelUpdateCheck(oldChannel, newKey);
                } else {
                    channelUpdateCheck(key, newKey);
                }
                channelToMap.put(key, newKey);
                refreshChannelToDataMap(channel.getRedisPush(), newKey, oldChannel);
            });
            log.info("原始推送频道别名设置完毕!");
        }
    }

    /**
     * 频道更新检查
     *
     * @param key    映射前频道
     * @param newKey 映射后频道
     */
    private void channelUpdateCheck(String key, String newKey) {
        // 删除对应key , 返回value 作为新key 的value
        if (channelToDataMap.containsKey(key)) {
            channelToDataMap.put(newKey, channelToDataMap.remove(key));
        }
    }

    /**
     * 刷新频道数据
     *
     * @param redisPush 是否进行推送
     * @param newKey    映射频道
     */
    private void refreshChannelToDataMap(Boolean redisPush, String newKey, String oldKey) {
        // 判断当前频道的数据是否需要进行推送
        if (Objects.nonNull(redisPush) && redisPush) {
            if (channelToDataMap.containsKey(newKey)) {
                channelToDataMap.put(newKey, channelToDataMap.remove(newKey));
            }
        }
    }

    /**
     * 刷新推送数据
     *
     * @param file 文件
     */
    public void refreshPushData(File file) {
        // 重新加载推送数据, 并刷新频道映射
        log.info("刷新推送数据中,并刷新频道别名....");
        loadPushData(file);
    }

    /**
     * 刷新频道
     *
     * @param file 文件
     */
    public void refreshChannel(File file) {
        // 清除映射, 重新加载映射文件
        log.info("刷新频道映射中....");
        loadChannelToDataMap(file);
    }

    /**
     * 停止指定推送的频道,并将推送的数据清除
     *
     * @param file 文件
     */
    public void stopChannelPush(File file) {
        //文件删除时触发, 将对应的推送数据一并删除
        log.info("停止 {} 数据的推送...", file.getName());
        String fileName = resetPushPath(file, ConfInfo.listenerFile);
        if (CollectionUtil.isEmpty(channelToMap)) {
            // 未配置映射,则直接通过文件名删除数据
            channelToDataMap.remove(fileName);
        } else {
            if (channelToMap.containsKey(fileName)) {
                // 拿到映射别名
                String key = channelToMap.get(fileName);
                channelToDataMap.remove(key);
            }
        }
    }

    /**
     * 全部停止, 并清除所有数据
     */
    public void stop() {
        log.info("即将停止全部推送...");
        channelToDataMap.clear();
        channelToMap.clear();
    }

    /**
     * 恢复原始推送频道
     */
    public void resetChannel() {
        if (!CollectionUtils.isEmpty(channelToMap)) {
            log.info("即将恢复原始推送频道,注意订阅频道格式...");
            // 获取频道映射
            channelToMap.forEach((oldKey, newKey) -> {
                // 还原原始频道
                if (channelToDataMap.containsKey(newKey)) {
                    channelToDataMap.put(oldKey, channelToDataMap.remove(newKey));
                }
            });
            // 清除映射
            channelToMap.clear();
        }
    }


    public Map<String, Object> getChannelToDataMap() {
        return channelToDataMap;
    }
}
